/*
 * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing;

import javax.swing.SortOrder;
import javax.swing.event.*;
import java.util.*;

/**
 * <code>RowSorter</code> provides the basis for sorting and filtering.
 * Beyond creating and installing a <code>RowSorter</code>, you very rarely
 * need to interact with one directly.  Refer to
 * {@link javax.swing.table.TableRowSorter TableRowSorter} for a concrete
 * implementation of <code>RowSorter</code> for <code>JTable</code>.
 * <p>
 * <code>RowSorter</code>'s primary role is to provide a mapping between
 * two coordinate systems: that of the view (for example a
 * <code>JTable</code>) and that of the underlying data source, typically a
 * model.
 * <p>
 * The view invokes the following methods on the <code>RowSorter</code>:
 * <ul>
 * <li><code>toggleSortOrder</code> &#151; The view invokes this when the
 * appropriate user gesture has occurred to trigger a sort.  For example,
 * the user clicked a column header in a table.
 * <li>One of the model change methods &#151; The view invokes a model
 * change method when the underlying model
 * has changed.  There may be order dependencies in how the events are
 * delivered, so a <code>RowSorter</code> should not update its mapping
 * until one of these methods is invoked.
 * </ul>
 * Because the view makes extensive use of  the
 * <code>convertRowIndexToModel</code>,
 * <code>convertRowIndexToView</code> and <code>getViewRowCount</code> methods,
 * these methods need to be fast.
 * <p>
 * <code>RowSorter</code> provides notification of changes by way of
 * <code>RowSorterListener</code>.  Two types of notification are sent:
 * <ul>
 * <li><code>RowSorterEvent.Type.SORT_ORDER_CHANGED</code> &#151; notifies
 * listeners that the sort order has changed.  This is typically followed
 * by a notification that the sort has changed.
 * <li><code>RowSorterEvent.Type.SORTED</code> &#151; notifies listeners that
 * the mapping maintained by the <code>RowSorter</code> has changed in
 * some way.
 * </ul>
 * <code>RowSorter</code> implementations typically don't have a one-to-one
 * mapping with the underlying model, but they can.
 * For example, if a database does the sorting,
 * <code>toggleSortOrder</code> might call through to the database
 * (on a background thread), and override the mapping methods to return the
 * argument that is passed in.
 * <p>
 * Concrete implementations of <code>RowSorter</code>
 * need to reference a model such as <code>TableModel</code> or
 * <code>ListModel</code>.  The view classes, such as
 * <code>JTable</code> and <code>JList</code>, will also have a
 * reference to the model.  To avoid ordering dependencies,
 * <code>RowSorter</code> implementations should not install a
 * listener on the model.  Instead the view class will call into the
 * <code>RowSorter</code> when the model changes.  For
 * example, if a row is updated in a <code>TableModel</code>
 * <code>JTable</code> invokes <code>rowsUpdated</code>.
 * When the model changes, the view may call into any of the following methods:
 * <code>modelStructureChanged</code>, <code>allRowsChanged</code>,
 * <code>rowsInserted</code>, <code>rowsDeleted</code> and
 * <code>rowsUpdated</code>.
 *
 * @param <M> the type of the underlying model
 * @see javax.swing.table.TableRowSorter
 * @since 1.6
 */
public abstract class RowSorter<M> {

  private EventListenerList listenerList = new EventListenerList();

  /**
   * Creates a <code>RowSorter</code>.
   */
  public RowSorter() {
  }

  /**
   * Returns the underlying model.
   *
   * @return the underlying model
   */
  public abstract M getModel();

  /**
   * Reverses the sort order of the specified column.  It is up to
   * subclasses to provide the exact behavior when invoked.  Typically
   * this will reverse the sort order from ascending to descending (or
   * descending to ascending) if the specified column is already the
   * primary sorted column; otherwise, makes the specified column
   * the primary sorted column, with an ascending sort order.  If
   * the specified column is not sortable, this method has no
   * effect.
   * <p>
   * If this results in changing the sort order and sorting, the
   * appropriate <code>RowSorterListener</code> notification will be
   * sent.
   *
   * @param column the column to toggle the sort ordering of, in terms of the underlying model
   * @throws IndexOutOfBoundsException if column is outside the range of the underlying model
   */
  public abstract void toggleSortOrder(int column);

  /**
   * Returns the location of <code>index</code> in terms of the
   * underlying model.  That is, for the row <code>index</code> in
   * the coordinates of the view this returns the row index in terms
   * of the underlying model.
   *
   * @param index the row index in terms of the underlying view
   * @return row index in terms of the view
   * @throws IndexOutOfBoundsException if <code>index</code> is outside the range of the view
   */
  public abstract int convertRowIndexToModel(int index);

  /**
   * Returns the location of <code>index</code> in terms of the
   * view.  That is, for the row <code>index</code> in the
   * coordinates of the underlying model this returns the row index
   * in terms of the view.
   *
   * @param index the row index in terms of the underlying model
   * @return row index in terms of the view, or -1 if index has been filtered out of the view
   * @throws IndexOutOfBoundsException if <code>index</code> is outside the range of the model
   */
  public abstract int convertRowIndexToView(int index);

  /**
   * Sets the current sort keys.
   *
   * @param keys the new <code>SortKeys</code>; <code>null</code> is a shorthand for specifying an
   * empty list, indicating that the view should be unsorted
   */
  public abstract void setSortKeys(List<? extends SortKey> keys);

  /**
   * Returns the current sort keys.  This must return a {@code
   * non-null List} and may return an unmodifiable {@code List}. If
   * you need to change the sort keys, make a copy of the returned
   * {@code List}, mutate the copy and invoke {@code setSortKeys}
   * with the new list.
   *
   * @return the current sort order
   */
  public abstract List<? extends SortKey> getSortKeys();

  /**
   * Returns the number of rows in the view.  If the contents have
   * been filtered this might differ from the row count of the
   * underlying model.
   *
   * @return number of rows in the view
   * @see #getModelRowCount
   */
  public abstract int getViewRowCount();

  /**
   * Returns the number of rows in the underlying model.
   *
   * @return number of rows in the underlying model
   * @see #getViewRowCount
   */
  public abstract int getModelRowCount();

  /**
   * Invoked when the underlying model structure has completely
   * changed.  For example, if the number of columns in a
   * <code>TableModel</code> changed, this method would be invoked.
   * <p>
   * You normally do not call this method.  This method is public
   * to allow view classes to call it.
   */
  public abstract void modelStructureChanged();

  /**
   * Invoked when the contents of the underlying model have
   * completely changed. The structure of the table is the same,
   * only the contents have changed. This is typically sent when it
   * is too expensive to characterize the change in terms of the
   * other methods.
   * <p>
   * You normally do not call this method.  This method is public
   * to allow view classes to call it.
   */
  public abstract void allRowsChanged();

  /**
   * Invoked when rows have been inserted into the underlying model
   * in the specified range (inclusive).
   * <p>
   * The arguments give the indices of the effected range.
   * The first argument is in terms of the model before the change, and
   * must be less than or equal to the size of the model before the change.
   * The second argument is in terms of the model after the change and must
   * be less than the size of the model after the change. For example,
   * if you have a 5-row model and add 3 items to the end of the model
   * the indices are 5, 7.
   * <p>
   * You normally do not call this method.  This method is public
   * to allow view classes to call it.
   *
   * @param firstRow the first row
   * @param endRow the last row
   * @throws IndexOutOfBoundsException if either argument is invalid, or <code>firstRow</code> &gt;
   * <code>endRow</code>
   */
  public abstract void rowsInserted(int firstRow, int endRow);

  /**
   * Invoked when rows have been deleted from the underlying model
   * in the specified range (inclusive).
   * <p>
   * The arguments give the indices of the effected range and
   * are in terms of the model <b>before</b> the change.
   * For example, if you have a 5-row model and delete 3 items from the end
   * of the model the indices are 2, 4.
   * <p>
   * You normally do not call this method.  This method is public
   * to allow view classes to call it.
   *
   * @param firstRow the first row
   * @param endRow the last row
   * @throws IndexOutOfBoundsException if either argument is outside the range of the model before
   * the change, or <code>firstRow</code> &gt; <code>endRow</code>
   */
  public abstract void rowsDeleted(int firstRow, int endRow);

  /**
   * Invoked when rows have been changed in the underlying model
   * between the specified range (inclusive).
   * <p>
   * You normally do not call this method.  This method is public
   * to allow view classes to call it.
   *
   * @param firstRow the first row, in terms of the underlying model
   * @param endRow the last row, in terms of the underlying model
   * @throws IndexOutOfBoundsException if either argument is outside the range of the underlying
   * model, or <code>firstRow</code> &gt; <code>endRow</code>
   */
  public abstract void rowsUpdated(int firstRow, int endRow);

  /**
   * Invoked when the column in the rows have been updated in
   * the underlying model between the specified range.
   * <p>
   * You normally do not call this method.  This method is public
   * to allow view classes to call it.
   *
   * @param firstRow the first row, in terms of the underlying model
   * @param endRow the last row, in terms of the underlying model
   * @param column the column that has changed, in terms of the underlying model
   * @throws IndexOutOfBoundsException if either argument is outside the range of the underlying
   * model after the change, <code>firstRow</code> &gt; <code>endRow</code>, or <code>column</code>
   * is outside the range of the underlying model
   */
  public abstract void rowsUpdated(int firstRow, int endRow, int column);

  /**
   * Adds a <code>RowSorterListener</code> to receive notification
   * about this <code>RowSorter</code>.  If the same
   * listener is added more than once it will receive multiple
   * notifications.  If <code>l</code> is <code>null</code> nothing
   * is done.
   *
   * @param l the <code>RowSorterListener</code>
   */
  public void addRowSorterListener(RowSorterListener l) {
    listenerList.add(RowSorterListener.class, l);
  }

  /**
   * Removes a <code>RowSorterListener</code>.  If
   * <code>l</code> is <code>null</code> nothing is done.
   *
   * @param l the <code>RowSorterListener</code>
   */
  public void removeRowSorterListener(RowSorterListener l) {
    listenerList.remove(RowSorterListener.class, l);
  }

  /**
   * Notifies listener that the sort order has changed.
   */
  protected void fireSortOrderChanged() {
    fireRowSorterChanged(new RowSorterEvent(this));
  }

  /**
   * Notifies listener that the mapping has changed.
   *
   * @param lastRowIndexToModel the mapping from model indices to view indices prior to the sort,
   * may be <code>null</code>
   */
  protected void fireRowSorterChanged(int[] lastRowIndexToModel) {
    fireRowSorterChanged(new RowSorterEvent(this,
        RowSorterEvent.Type.SORTED, lastRowIndexToModel));
  }

  void fireRowSorterChanged(RowSorterEvent event) {
    Object[] listeners = listenerList.getListenerList();
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == RowSorterListener.class) {
        ((RowSorterListener) listeners[i + 1]).
            sorterChanged(event);
      }
    }
  }

  /**
   * SortKey describes the sort order for a particular column.  The
   * column index is in terms of the underlying model, which may differ
   * from that of the view.
   *
   * @since 1.6
   */
  public static class SortKey {

    private int column;
    private SortOrder sortOrder;

    /**
     * Creates a <code>SortKey</code> for the specified column with
     * the specified sort order.
     *
     * @param column index of the column, in terms of the model
     * @param sortOrder the sorter order
     * @throws IllegalArgumentException if <code>sortOrder</code> is <code>null</code>
     */
    public SortKey(int column, SortOrder sortOrder) {
      if (sortOrder == null) {
        throw new IllegalArgumentException(
            "sort order must be non-null");
      }
      this.column = column;
      this.sortOrder = sortOrder;
    }

    /**
     * Returns the index of the column.
     *
     * @return index of column
     */
    public final int getColumn() {
      return column;
    }

    /**
     * Returns the sort order of the column.
     *
     * @return the sort order of the column
     */
    public final SortOrder getSortOrder() {
      return sortOrder;
    }

    /**
     * Returns the hash code for this <code>SortKey</code>.
     *
     * @return hash code
     */
    public int hashCode() {
      int result = 17;
      result = 37 * result + column;
      result = 37 * result + sortOrder.hashCode();
      return result;
    }

    /**
     * Returns true if this object equals the specified object.
     * If the specified object is a <code>SortKey</code> and
     * references the same column and sort order, the two objects
     * are equal.
     *
     * @param o the object to compare to
     * @return true if <code>o</code> is equal to this <code>SortKey</code>
     */
    public boolean equals(Object o) {
      if (o == this) {
        return true;
      }
      if (o instanceof SortKey) {
        return (((SortKey) o).column == column &&
            ((SortKey) o).sortOrder == sortOrder);
      }
      return false;
    }
  }
}
